/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * http//www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is ART Ontology API (Sesame2 Implementation).
 *
 * The Initial Developer of the Original Code is University of Roma Tor Vergata.
 * Portions created by University of Roma Tor Vergata are Copyright (C) 2007.
 * All Rights Reserved.
 *
 * ART Ontology API (Sesame2 Implementation) was developed by the Artificial Intelligence Research Group
 * (art.uniroma2.it) at the University of Roma Tor Vergata
 * Current information about the ART Ontology API (Sesame2 Implementation) can be obtained at 
 * http//art.uniroma2.it/owlart
 *
 */
package it.uniroma2.art.owlart.sesame2impl.factory;

import it.uniroma2.art.owlart.exceptions.ModelCreationException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.exceptions.VocabularyInitializationException;
import it.uniroma2.art.owlart.models.BaseRDFTripleModel;
import it.uniroma2.art.owlart.models.ModelFactory;
import it.uniroma2.art.owlart.models.OWLArtModelFactory;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.models.RDFModel;
import it.uniroma2.art.owlart.models.RDFSModel;
import it.uniroma2.art.owlart.models.SKOSModel;
import it.uniroma2.art.owlart.models.SKOSXLModel;
import it.uniroma2.art.owlart.models.TripleQueryModelHTTPConnection;
import it.uniroma2.art.owlart.models.UnloadableModelConfigurationException;
import it.uniroma2.art.owlart.models.UnsupportedModelConfigurationException;
import it.uniroma2.art.owlart.sesame2impl.models.BaseRDFModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.OWLModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.RDFModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.RDFSModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.SKOSModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.SKOSXLModelSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.TripleQueryModelHTTPConnectionSesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2DirectAccessModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2InMemoryModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2ModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2NativeModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2NonPersistentInMemoryModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2PersistentInMemoryModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2RemoteModelConfiguration;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;

import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.config.RepositoryConfigException;
import org.openrdf.repository.manager.RemoteRepositoryManager;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.sail.SailException;
import org.openrdf.sail.helpers.NotifyingSailBase;
import org.openrdf.sail.inferencer.fc.DirectTypeHierarchyInferencer;
import org.openrdf.sail.inferencer.fc.ForwardChainingRDFSInferencer;
import org.openrdf.sail.memory.MemoryStore;
import org.openrdf.sail.nativerdf.NativeStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class implements {@link ModelFactory}<br/>
 * 
 * A fast and easy way to load an RDF/RDFS/OWL Model by using this Sesame2 implementation is to wrap it
 * through the OWL API convenience class {@link OWLArtModelFactory}, which handles standard configuration of
 * models of the RDF family, like loading proper vocabularies, setting baseuri/defnamespace of the loaded
 * model etc... <br/>
 * This is the code to do that:
 * 
 * <pre>
 * ModelFactory fact = OWLArtModelFactory.createModelFactory(&lt;an instance of this class&gt;);
 * </pre>
 * 
 * @author Armando Stellato <stellato@info.uniroma2.it>
 * 
 */
public class ARTModelFactorySesame2Impl implements ModelFactory<Sesame2ModelConfiguration> {

	protected static Logger logger = LoggerFactory.getLogger(ARTModelFactorySesame2Impl.class);

	ArrayList<Class<? extends Sesame2ModelConfiguration>> supportedConfigurationClasses;

	protected boolean populatingW3CVocabularies = true;

	public ARTModelFactorySesame2Impl() {
		supportedConfigurationClasses = new ArrayList<Class<? extends Sesame2ModelConfiguration>>();
		supportedConfigurationClasses.add(Sesame2PersistentInMemoryModelConfiguration.class);
		supportedConfigurationClasses.add(Sesame2NonPersistentInMemoryModelConfiguration.class);
		supportedConfigurationClasses.add(Sesame2NativeModelConfiguration.class);
		supportedConfigurationClasses.add(Sesame2RemoteModelConfiguration.class);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.uniroma2.art.owlart.models.ModelFactory#closeModel(it.uniroma2.art.owlart.models.BaseRDFTripleModel)
	 */
	public void closeModel(BaseRDFTripleModel rep) throws ModelUpdateException {
		rep.close();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFBaseModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public BaseRDFModelSesame2Impl loadRDFBaseModel(String baseuri, String repositoryDirectory,
			Sesame2ModelConfiguration conf) throws ModelCreationException {

		File dataDir = new File(repositoryDirectory);
		if (!dataDir.exists())
			throw new ModelCreationException("you must specify an existig directory for this repository; "
					+ dataDir + " does not exist");

		NotifyingSailBase baseSail;
		// SailRepository myRepository;
		Repository myRepository;
		boolean rdfsReasoning = false;
		boolean directTypeReasoning = false;

		logger.info("creating Sesame2 Model");

		if (conf instanceof Sesame2DirectAccessModelConfiguration) {

			if (conf instanceof Sesame2InMemoryModelConfiguration) {
				baseSail = new MemoryStore(dataDir);
				if (conf instanceof Sesame2PersistentInMemoryModelConfiguration) {
					((MemoryStore) baseSail).setPersist(true);
					long syncDelay = ((Sesame2PersistentInMemoryModelConfiguration) conf).syncDelay;
					logger.info("syncDelay: " + syncDelay);
					((MemoryStore) baseSail).setSyncDelay(syncDelay);
				} else
					((MemoryStore) baseSail).setPersist(false);

			} else if (conf instanceof Sesame2NativeModelConfiguration) {
				baseSail = new NativeStore(dataDir);
				((NativeStore) baseSail).setForceSync(((Sesame2NativeModelConfiguration) conf).forceSync);
				((NativeStore) baseSail)
						.setTripleIndexes(((Sesame2NativeModelConfiguration) conf).tripleIndexes);

			} else
				throw new ModelCreationException("unknown configuration type: " + conf.getClass());

			Sesame2DirectAccessModelConfiguration daconf = (Sesame2DirectAccessModelConfiguration) conf;

			if (!daconf.directTypeInference && !daconf.rdfsInference)
				myRepository = new SailRepository(baseSail);
			else if (daconf.directTypeInference && !daconf.rdfsInference)
				myRepository = new SailRepository(new DirectTypeHierarchyInferencer(baseSail));
			else if (!daconf.directTypeInference && daconf.rdfsInference)
				myRepository = new SailRepository(new ForwardChainingRDFSInferencer(baseSail));
			else
				// both of them active
				myRepository = new SailRepository(new DirectTypeHierarchyInferencer(
						new ForwardChainingRDFSInferencer(baseSail)));

			rdfsReasoning = daconf.rdfsInference;
			directTypeReasoning = daconf.directTypeInference;

		} else { // remote access

			Sesame2RemoteModelConfiguration remModelConf = (Sesame2RemoteModelConfiguration) conf;

			try {
				RemoteRepositoryManager repositoryManager = RemoteRepositoryManager.getInstance(
						remModelConf.serverURL, remModelConf.username, remModelConf.password);
				myRepository = repositoryManager.getRepository(remModelConf.repositoryId);
				if (myRepository == null)
					throw new ModelCreationException("there is no repository with id: "
							+ remModelConf.repositoryId);

			} catch (RepositoryException e) {
				throw new ModelCreationException("Problem with remote connection: " + e.getMessage());
			} catch (RepositoryConfigException e) {
				throw new ModelCreationException("Problem with remote connection: " + e.getMessage());
			}

			rdfsReasoning = true;
			directTypeReasoning = true;

			// TODO
			// ricordati poi, qualora fosse possibile appurare dal server se un repository è configurato con
			// rdfs reasoning/direct type o meno (OWLIM dovrebbe avere di serie un sottoinsieme di OWL, ma
			// forse va configurato dal server, e forse, dico forse, il client può, al momento della
			// connessione o cmq con una query successiva, sapere che livello di reasoning ha.
			// io per ora metto true perchè immagino che OWLIM abbia rdfs reasoning almeno.

		}

		try {
			myRepository.initialize();
		} catch (RepositoryException e) {
			throw new ModelCreationException(e.getMessage());
		}

		logger.info("Sesame2 RDF Model initialization...");
		try {
			BaseRDFModelSesame2Impl rep = new BaseRDFModelSesame2Impl(myRepository, rdfsReasoning,
					directTypeReasoning);
			return rep;
		} catch (SailException e) {
			throw new ModelCreationException(e.getMessage());
		} catch (RepositoryException e) {
			throw new ModelCreationException(e.getMessage());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public RDFModel loadRDFModel(String baseuri, String repositoryDirectory, Sesame2ModelConfiguration conf)
			throws ModelCreationException {
		BaseRDFModelSesame2Impl baserep = loadRDFBaseModel(baseuri, repositoryDirectory, conf);
		try {
			RDFModelSesame2Impl rep = new RDFModelSesame2Impl(baserep);
			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadRDFSModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public RDFSModel loadRDFSModel(String baseuri, String repositoryDirectory, Sesame2ModelConfiguration conf)
			throws ModelCreationException {
		BaseRDFModelSesame2Impl baserep = loadRDFBaseModel(baseuri, repositoryDirectory, conf);
		try {
			RDFSModelSesame2Impl rep = new RDFSModelSesame2Impl(baserep);
			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadOWLModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public OWLModel loadOWLModel(String baseuri, String repositoryDirectory, Sesame2ModelConfiguration conf)
			throws ModelCreationException {
		BaseRDFModelSesame2Impl baserep = loadRDFBaseModel(baseuri, repositoryDirectory, conf);
		try {
			OWLModelSesame2Impl rep = new OWLModelSesame2Impl(baserep);

			// ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);
			// logger.debug("owl namespace: " + owl.getURI());
			// rep.addRDF(Resources.class.getResource("owl.rdfs"), OWL.NAMESPACE, RDFFormat.RDFXML, owl);

			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.uniroma2.art.owlart.models.ModelFactory#loadSKOSModel(java.lang.String, java.lang.String,
	 * boolean)
	 */
	public SKOSModel loadSKOSModel(String baseuri, String persistenceDirectory, Sesame2ModelConfiguration conf)
			throws ModelCreationException {
		BaseRDFModelSesame2Impl baserep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);
		try {
			SKOSModelSesame2Impl rep = new SKOSModelSesame2Impl(baserep);

			// ARTURIResource skos = rep.createURIResource(SKOS.NAMESPACE);
			// ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);
			// rep.addRDF(Resources.class.getResource("owl.rdfs"), OWL.NAMESPACE, RDFFormat.RDFXML, owl);
			// rep.addRDF(Resources.class.getResource("skos.rdf"), SKOS.NAMESPACE, RDFFormat.RDFXML, skos);

			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	public SKOSXLModel loadSKOSXLModel(String baseuri, String persistenceDirectory,
			Sesame2ModelConfiguration conf) throws ModelCreationException {
		BaseRDFModelSesame2Impl baserep = loadRDFBaseModel(baseuri, persistenceDirectory, conf);
		try {
			SKOSXLModelSesame2Impl rep = new SKOSXLModelSesame2Impl(baserep);

			// ARTURIResource owl = rep.createURIResource(OWL.NAMESPACE);
			// ARTURIResource skos = rep.createURIResource(SKOS.NAMESPACE);
			// ARTURIResource skosxl = rep.createURIResource(SKOSXL.NAMESPACE);
			// logger.debug("owl namespace: " + owl.getURI());
			// logger.debug("skos namespace: " + skos.getURI());
			// logger.debug("skos xl namespace: " + skosxl.getURI());
			// rep.addRDF(Resources.class.getResource("owl.rdfs"), OWL.NAMESPACE, RDFFormat.RDFXML, owl);
			// rep.addRDF(Resources.class.getResource("skos.rdf"), SKOS.NAMESPACE, RDFFormat.RDFXML, skos);
			// rep.addRDF(Resources.class.getResource("skos-xl.rdf"), SKOSXL.NAMESPACE, RDFFormat.RDFXML,
			// skosxl);

			return rep;
		} catch (VocabularyInitializationException e) {
			throw new ModelCreationException(e);
		}
	}

	public Collection<Class<? extends Sesame2ModelConfiguration>> getModelConfigurations() {
		return supportedConfigurationClasses;
	}

	public <MCImpl extends Sesame2ModelConfiguration> MCImpl createModelConfigurationObject(
			Class<MCImpl> mcclass) throws UnsupportedModelConfigurationException,
			UnloadableModelConfigurationException {
		logger.debug("creating ModelConfigurationObject");
		if (supportedConfigurationClasses.contains(mcclass)) {
			// if (true) {
			try {

				// logger.debug("class loader of: " + this.getClass().getSimpleName() + " instance: " +
				// this.getClass().getClassLoader());
				// logger.debug("class loader of static: " + ARTModelFactorySesame2Impl.class.getSimpleName()
				// + " : " + ARTModelFactorySesame2Impl.class.getClassLoader());
				// logger.debug("class loader of passed model configuration class: " + mcclass.getSimpleName()
				// + "inside: " + this.getClass().getSimpleName() + " = " + mcclass.getClassLoader());
				// logger.debug("class loader of inner model configuration class: " +
				// innerMConfCls.getSimpleName() + "inside: " + this.getClass().getSimpleName() + " = " +
				// innerMConfCls.getClassLoader());

				logger.debug("requested model configuration class: " + mcclass);
				// logger.debug("inner model configuration class: " + innerMConfCls );

				MCImpl mConf = (MCImpl) mcclass.newInstance();

				// MCImpl mConf = mcclass.newInstance();

				// logger.debug("mConf: " + mConf );
				// return mcclass.newInstance();
				return mConf;
			} catch (InstantiationException e) {
				throw new UnloadableModelConfigurationException(mcclass);
			} catch (IllegalAccessException e) {
				throw new UnloadableModelConfigurationException(mcclass);
			}
		} else
			throw new UnsupportedModelConfigurationException(this, mcclass);

	}

	public TripleQueryModelHTTPConnection loadTripleQueryHTTPConnection(String endpointURL)
			throws ModelCreationException {
		return new TripleQueryModelHTTPConnectionSesame2Impl(endpointURL);
	}

	public void setPopulatingW3CVocabularies(boolean pref) {
		populatingW3CVocabularies = pref;
	}

	public boolean isPopulatingW3CVocabularies() {
		return populatingW3CVocabularies;
	}

}
